using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using Weazel.Gezel.Model;
using Weazel.Gezel.Model.Errors;
using Weazel.Gezel.Parser;

using Weazel.Microblaze.Elf;
using Weazel.Microblaze.Simulator;
using Weazel.Microblaze.Simulator.Engine;

namespace MicroblazeConsoleSimulator
{
    class Program
    {
        private const string ExecutableName = "mb-sim";

        private static Configuration configuration = new Configuration();

        static Output gezelDisplayOutput = new Output("Gezel $display");
        static Output microblazeStdOutput = new Output("Microblaze stdout/stderr");
        static Output opbCommunicationOutput = new Output("OPB Communication");

        static void printUsageAndExit()
        {
            Console.WriteLine();
            Console.WriteLine(" Usage: mb-sim [elf-file] [gezel-file] {options ...} ");
            Console.WriteLine();
            Console.WriteLine(" By default all output will be shown in the console. To alter");
            Console.WriteLine(" this behavior use the following options:");
            Console.WriteLine();
            Console.WriteLine("  -gout [file]   Redirect gezel $display output to [file]");
            Console.WriteLine("  -cout [file]   Redirect MB stdout and stderr to [file]");
            Console.WriteLine("  -oout [file]   Redirect OPB communication log to [file]");
            Console.WriteLine();
            Console.WriteLine(" Other options:");
            Console.WriteLine();
            Console.WriteLine("  -diec [file]   Dump the number of times the instructions of the");
            Console.WriteLine("                 microblaze have been executed to [file]");
            Console.WriteLine();
            Console.WriteLine("  -v             Verbose, prints debug information");

            System.Environment.Exit(1);
        }

        static Model processGezelInput()
        {
            Model model = null;

            if (configuration.GezelFileName != null)
            {
                string description = null;

                // reading description from a fdl file
                using (StreamReader reader = new StreamReader(configuration.GezelFileName))
                {
                    description = reader.ReadToEnd();
                }

                // parse the file
                List<ParseError> parseErrors = new List<ParseError>();
                model = ParseModel.Parse(ref parseErrors, description, configuration.GezelFileName);

                // print out any parse errors
                if (parseErrors.Count > 0)
                {
                    Console.WriteLine("Simulation was aborted because of these parse error(s):");
                    foreach (ParseError error in parseErrors)
                    {
                        Console.WriteLine();
                        Console.WriteLine(error.ToString());
                    }

                    // if the model contained errors
                    // don't try to setup simulation
                    Environment.Exit(1);
                }

                // verify the file
                List<VerificationError> verificationErrors = null;
                List<VerificationWarning> warnings = null;

                model.Verify(ref verificationErrors, ref warnings);

                // print errors if any
                if (verificationErrors.Count > 0)
                {
                    Console.Error.WriteLine("Simulation was aborted because of the these verification error(s):");
                    foreach (VerificationError error in verificationErrors)
                    {
                        Console.Error.WriteLine();
                        Console.Error.WriteLine(error.ToString());
                    }

                    // if the model contained errors
                    // don't try to setup simulation
                    Environment.Exit(1);
                }

                if (warnings.Count > 0)
                {
                    Console.Error.WriteLine("The following warning(s) was detected during verification:");
                    foreach (VerificationWarning warning in warnings)
                    {
                        Console.Error.WriteLine();
                        Console.Error.WriteLine(warning.ToString());
                    }
                }
            }

            return model;
        }


        static void parseArguments(string[] args)
        {
            int index = 0;

            while (index < args.Length)
            {
                if (args[index] == ExecutableName)
                {
                    index++;
                }
                else if (args[index] == "-gout")
                {
                    if (index + 1 >= args.Length)
                    {
                        Console.WriteLine("Missing -gout argument");
                        printUsageAndExit();
                    }

                    if (!gezelDisplayOutput.RedirectToFile(args[index + 1]))
                    {
                        Console.WriteLine("Cannot open output file: '{0}'", gezelDisplayOutput.Filename);
                        System.Environment.Exit(1);
                    }

                    index += 2;
                }
                else if (args[index] == "-cout")
                {
                    if (index + 1 >= args.Length)
                    {
                        Console.WriteLine("Missing -cout argument");
                        printUsageAndExit();
                    }

                    if (!microblazeStdOutput.RedirectToFile(args[index + 1]))
                    {
                        Console.WriteLine("Cannot open output file: '{0}'", microblazeStdOutput.Filename);
                        System.Environment.Exit(1);
                    }

                    index += 2;
                }
                else if (args[index] == "-oout")
                {
                    if (index + 1 >= args.Length)
                    {
                        Console.WriteLine("Missing -oout argument");
                        printUsageAndExit();
                    }

                    if (!opbCommunicationOutput.RedirectToFile(args[index + 1]))
                    {
                        Console.WriteLine("Cannot open output file: '{0}'", opbCommunicationOutput.Filename);
                        System.Environment.Exit(1);
                    }

                    index += 2;
                }
                else if (args[index] == "-diec")
                {
                    if (index + 1 >= args.Length)
                    {
                        Console.WriteLine("Missing -diec argument");
                        printUsageAndExit();
                    }

                    configuration.InstructionExecutionCountOutputFile = args[index + 1];

                    index += 2;
                }
                else if (args[index].StartsWith("-"))
                {
                    Console.WriteLine("Unknown option '{0}'", args[index]);
                    printUsageAndExit();
                }
                else
                {
                    // should be name of elf file

                    if (args[index].ToLower().EndsWith(".elf"))
                    {
                        configuration.ElfFileName = args[index];
                    }
                    else if (args[index].ToLower().EndsWith(".fdl"))
                    {
                        configuration.GezelFileName = args[index];
                    }
                    else
                    {
                        Console.WriteLine("Unsupported file format: '{0}'", args[index]);
                        printUsageAndExit();
                    }

                    index++;
                }
            }

            if (configuration.ElfFileName == null)
            {
                Console.WriteLine("No elf file specified");
                printUsageAndExit();
            }
        }

        static void CleanUp()
        {
            opbCommunicationOutput.Close();
            gezelDisplayOutput.Close();
            microblazeStdOutput.Close();
        }

        [System.MTAThread()]
        static void Main(string[] args)
        {
            Console.WriteLine("Microblaze simulator v.02");
            Console.WriteLine("------------------------------------------------------");
            Console.WriteLine();

            parseArguments(args);

            Console.WriteLine("Input:");
            Console.WriteLine("------------------------------------------------------");
            Console.WriteLine("  Elf: {0}", configuration.ElfFileName);
            Console.WriteLine("  Fdl: {0}", configuration.GezelFileName);
            Console.WriteLine();

            Console.WriteLine("Output:");
            Console.WriteLine("------------------------------------------------------");
            Console.WriteLine("  Microblaze stdout/stderr: {0}", microblazeStdOutput.Target);
            Console.WriteLine("  Gezel $display:           {0}", gezelDisplayOutput.Target);
            Console.WriteLine("  OPB operations:           {0}", opbCommunicationOutput.Target);
            Console.WriteLine();

            // if set open and process the Gezel file
            configuration.GezelModel = processGezelInput();

            // open the elf file            
            try
            {
                configuration.ElfFile = ElfFile.OpenAndProcessFile(configuration.ElfFileName);
            }
            catch (Exception e)
            {
                Console.WriteLine("Unable to open elf file ({0})", e.Message);
                System.Environment.Exit(1);
            }

            SimulationContext simulator = new SimulationContext(configuration);

            /*
         // set gezel model BEFORE calling initialize
         simulator.GezelModel = model;

         simulator.OpbIpifOutputWriter = opbCommunicationOutput.TextWriter;
         simulator.ConsoleOutputWriter = microblazeStdOutput.TextWriter;
             * */

            try
            {

                if (!simulator.Initialize())
                {
                    Console.WriteLine("Failed to initialize microblaze simulator");

                    CleanUp();
                    System.Environment.Exit(1);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine();
                Console.WriteLine();
                Console.Write("Error encountered while initializing microblaze simulator: ");
                Console.WriteLine(e.Message);

                Console.WriteLine(e.StackTrace);

                CleanUp();
                System.Environment.Exit(1);


            }


            /*
            if(simulator.GezelSimulation != null)
              simulator.GezelSimulation.OutputWriter = gezelDisplayOutput.TextWriter;
             * */

            Console.WriteLine("Simulating (press [ctrl+x] to terminate)");
            Console.WriteLine("------------------------------------------------------");
            Console.WriteLine();

            Thread t = new Thread(new ThreadStart(SimulationAbortListenerThreadBody));            
            t.Start();

            try
            {
                while (run)
                    simulator.Step();
            }
            catch (Exception e)
            {
                Console.WriteLine();
                Console.WriteLine();
                Console.WriteLine("------------------------------------------------------");
                Console.Write("Simulation aborted: ");
                Console.WriteLine(e.Message);
                Console.WriteLine("------------------------------------------------------");
                Console.WriteLine(e.StackTrace);
                
                System.Environment.Exit(1);
            }
            finally
            {
                // abort the abort listner thread
                t.Abort();

                CleanUp();

                simulator.CleanUp();
            }

            Console.WriteLine();
            Console.WriteLine("------------------------------------------------------");
            Console.WriteLine("Simulation terminated");
        }

        private static bool run = true;

        private static void SimulationAbortListenerThreadBody()
        {
            while (true)
            {
                ConsoleKeyInfo keyInfo = Console.ReadKey(true);
                if (keyInfo.Key == ConsoleKey.X && keyInfo.Modifiers == ConsoleModifiers.Control)
                {                    
                    run = false;
                    return;
                }
            }                        
        }
    }
}
